package lion.dev.io;

import java.io.DataInput;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.io.FileUtils;

/**
 *
 * 同 ByteArrayInputStream,不同在于该类具有更灵活的mark,reset方法，和InputStream不具有的seek方法，
 * readString等好用的方法。
 *
 *
 * @author lion
 * @email hl_0074@sina.com
 * @date 2014年4月18日
 */
public class ByteArrayReader implements DataInput {

	private byte[] content;
	private int pos = 0;
	private int mark = 0;
	private ByteOrder order;
	private Map<String, Integer> marks = new HashMap<>();

	public ByteArrayReader(byte[] src) {

		this.content = src;
		this.order = ByteOrder.BIG_ENDIAN;
	}

	public ByteArrayReader(String path) throws IOException {

		this(FileUtils.readFileToByteArray(new File(path)));
	}

	public void order(ByteOrder order) {

		this.order = order;
	}

	public void seek(int offset) {

		this.pos = offset;
	}

	@Override
	public void readFully(byte[] target) throws IOException {

		System.arraycopy(content, pos, target, 0, target.length);
		this.pos += target.length;
	}

	@Override
	public void readFully(byte[] target, int offset, int len) throws IOException {

		if (offset + len > this.content.length) {
			len = this.content.length - offset;
		}
		System.arraycopy(content, offset, target, 0, len);
		this.pos = offset + len;
	}

	@Override
	public int skipBytes(int num) throws IOException {

		int pos = Math.min(this.pos + num, this.content.length);
		int result = pos - this.pos;
		this.pos = pos;

		return result;
	}

	@Override
	public boolean readBoolean() throws IOException {

		return this.readByte() != 0;
	}

	@Override
	public byte readByte() throws IOException {

		if (this.pos >= this.content.length) { return -1; }
		byte b = this.content[this.pos];
		this.pos++;

		return b;
	}

	public byte[] readBytes(int len) {

		if (this.pos + len > this.content.length) {
			len = this.content.length - this.pos;
		}
		byte[] target = new byte[len];
		System.arraycopy(content, this.pos, target, 0, len);
		this.pos += len;

		return target;
	}

	@Override
	public int readUnsignedByte() throws IOException {

		return this.readByte() & 0xff;
	}

	@Override
	public short readShort() throws IOException {

		byte[] buf = new byte[2];
		readFully(buf);

		ByteBuffer byteBuffer = wrapeByteBuffer(buf);
		return byteBuffer.getShort();
	}

	@Override
	public int readUnsignedShort() throws IOException {

		int i = this.readUnsignedByte();
		int j = this.readUnsignedByte();
		if ((i | j) < 0) { throw new EOFException(); }
		return (i << 8) + j;
	}

	@Override
	public char readChar() throws IOException {

		byte[] buff = new byte[2];
		readFully(buff);
		ByteBuffer byteBuffer = wrapeByteBuffer(buff);
		return byteBuffer.getChar();
	}

	@Override
	public int readInt() throws IOException {

		byte[] buf = new byte[4];
		readFully(buf);
		ByteBuffer byteBuffer = wrapeByteBuffer(buf);
		return byteBuffer.getInt();
	}

	@Override
	public long readLong() throws IOException {

		byte[] buffer = new byte[8];
		readFully(buffer, 0, 8);

		ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);

		if (this.order != null) {
			byteBuffer.order(this.order);
		}

		return byteBuffer.getLong();
	}

	public int readLong3() throws IOException {

		byte[] b3 = new byte[3];
		readFully(b3);
		return toLong3(b3);
	}

	public int toLong3(byte[] b3) {

		byte[] buf = new byte[4];
		System.arraycopy(b3, 0, buf, 0, 3);

		ByteBuffer byteBuffer = ByteBuffer.wrap(buf);
		if (this.order != null) {
			byteBuffer.order(this.order);
		}

		return byteBuffer.getInt();
	}

	@Override
	public float readFloat() throws IOException {

		return Float.intBitsToFloat(readInt());
	}

	@Override
	public double readDouble() throws IOException {

		return Double.longBitsToDouble(readLong());
	}

	@Override
	public String readLine() {

		try {
			return readLine("UTF-8");
		} catch (UnsupportedEncodingException e) {
		}
		return "";
	}

	public String readLine(String charset) throws UnsupportedEncodingException {

		int end = this.pos;
		while (this.content[end] != '\n' && this.content[end] != '\r' && end < this.content.length) {
			end++;
		}
		this.pos = end;
		return new String(this.content, this.pos, end, charset);
	}

	@Override
	public String readUTF() throws IOException {

		return readString();
	}

	public String readString(String charset) throws UnsupportedEncodingException {

		int end = this.pos;
		while (this.content[end++] != 0 && end < this.content.length) {
		}

		String str = new String(this.content, this.pos, end - this.pos, charset);

		this.pos = end;
		return str;
	}

	public String readString() {

		try {
			return readString("UTF-8");
		} catch (UnsupportedEncodingException e) {
		}
		return "";
	}

	public void reset() throws IOException {

		this.pos = this.mark;
	}

	public void reset(String name) throws IOException {

		if (this.marks.get(name) != null) {
			this.pos = this.marks.get(name);
		} else {
			this.pos = this.mark;
		}
	}

	public void mark(int mark) throws IOException {

		this.mark = mark;
	}

	public void mark() throws IOException {

		this.mark = this.pos;
	}

	public void mark(String name) throws IOException {

		this.mark = this.pos;
		this.marks.put(name, this.mark);
	}

	public void mark(String name, int pos) throws IOException {

		this.mark = pos;
		this.marks.put(name, this.mark);
	}

	public boolean markSupported() {

		return true;
	}

	public int position() {

		return this.pos;
	}

	private ByteBuffer wrapeByteBuffer(byte[] buf) {

		ByteBuffer byteBuffer = ByteBuffer.wrap(buf);
		if (this.order != null) {
			byteBuffer.order(order);
		}
		return byteBuffer;
	}
}
